package peace.htmllooker
{
import flash.events.TextEvent;
import flash.net.URLRequest;
import flash.net.navigateToURL;
import flash.text.StyleSheet;

import peace.util.Logger;


[Event(name="link", type="flash.events.TextEvent")]
/**
 * Главный Box с выравниванием блоков в столбик
 */
public class Htmllooker extends VerticalHtmlBox
{
	/**
	 * формат для кастомных ссылок, несущих произвольнут текстовую информацию <br>
	 * и не требующих web-перехода. <br>
	 * <br>
	 * Обрабатываются такие ссылки выше по иерархии объектов.
	 */	
	public static const DATA_LINK_PREFIX:String = "DATA::";
	/**
	 * Разделитель при обработке ссылок
	 * 
	 * @see process
	 */	
	public static const LINK_TARGET_DELIMITER:String = "[;]";
	/**
	 * 
	 */	
	protected static const DEFAULT_CSS:StyleSheet = new StyleSheet();
	DEFAULT_CSS.parseCSS(	"a {color: #0000ff; text-decoration: underline;}"
						+	" html {fontFamily:Arial; fontSize:12;}"
						+   " h1 {fontFamily:Arial; fontSize:20; fontWeight:bold;}"
						+   " h2 {fontFamily:Arial; fontSize:18; fontWeight:bold;}"
						+   " h3 {fontFamily:Arial; fontSize:16; fontWeight:bold;}"
						+   " h4 {fontFamily:Arial; fontSize:14; fontWeight:bold;}"
						+   " h5 {fontFamily:Arial; fontSize:12; fontWeight:bold;}"
						+   " h6 {fontFamily:Arial; fontSize:10; fontWeight:bold;}");
	
	
	/**
	 * Базовая фабрика производства поддерживаемых узлов
	 */	
	public static var nodeFactory:NodeFactory = new NodeFactory();
	/**
	 * 
	 */	
	protected static var _formControlClasses:FormControlClasses;
	/**
	 * текущие скины для контролов формы
	 */
	public static function get formControlClasses ():FormControlClasses
	{
		if (!_formControlClasses)
		{
			_formControlClasses = new FormControlClasses();
		}
		return _formControlClasses;
	}
	
	/**
	 * 
	 * @param xml
	 * @param css
	 * 
	 */	
	public function Htmllooker(xml:XML = null, css:StyleSheet = null)
	{
		super(xml, css);
	}
	/**
	 * 
	 * @param xml
	 * @param css
	 * 
	 */
	override protected function init(xml:XML, css:StyleSheet):void
	{
		css = css || DEFAULT_CSS;
		xml = processXml(xml);

		// обрабатываем ссылки
		// если это лукер верхнего уровня
		addEventListener(TextEvent.LINK, onLink, false,0,true);
		
		super.init(xml, css);
	}
	/**
	 * Выносим для использования в init() и update()  
	 * 
	 * @param xml
	 * @return xml
	 * 
	 */	
	protected function processXml (xml:XML):XML
	{
		xml = xml || <body/>;
		xml = (new XmlPreprocessor(nodeFactory)).process(xml);
		
		// читаем свойства
		if (xml.@bgcolor != undefined) 
		{
			hasBackground = true;
			backgroundColor = HtmlUtils.htmlColor_To_0xcolor(xml.@bgcolor);
		}
		
		return xml;
	}

	/**
	 * Обновление контента
	 *  
	 * @param html
	 * 
	 */	
	public function update (xml:XML, css:StyleSheet = null):void
	{
		css = css || DEFAULT_CSS;
		xml = processXml(xml);

		// чистим старых детей
		container.removeAll();

		createChildren (xml, css);
		
		// выставляем ширину
		if (container.userWidth)
		{
			applyWidth (container.userWidth);
		}
		// выставляем высоту
		if (container.userHeight)
		{
			applyHeight (container.userHeight);
		}

		// выравниваем
		needArrange = true;
	}
	/**
	 * На всех ссылках висят события
	 * Обрабатываем в соответствии с @target
	 * 
	 * @param e TextEvent
	 * 
	 */	
	protected function onLink (e:TextEvent):void
	{
		// разбираем ссылку на таргет и ссылку
		var arr:Array = e.text.split (LINK_TARGET_DELIMITER); 
		var target:String = arr[0];
		var link:String = arr[1];
		
		// меняем e.text для всех желающих слушать событие выше
		e.text = link;
		
		// проверяем, не кастомная ли ссылка
		if (link.indexOf(DATA_LINK_PREFIX) == 0 || link.indexOf(DATA_LINK_PREFIX.toLowerCase()) == 0)
		{
			return;
		}
		
		var b:Browser = browser;
		// открываем внутри себя
		if (b && target != "_blank")
		{
			if (link.charAt(0) == "/" && b.url)
			{
				b.url = b.url.substring(0, b.url.indexOf("/", "https://".length)) + link;
			}
			else
			{
				b.url = link;
			}
		}
		else
		{
			try
			{
				navigateToURL(new URLRequest(link), target);
			}
			catch (error:Error)
			{
				Logger.error(error.message);
			}
		}
	}
	/**
	 * Скинование контролов формы
	 *  
	 * @param button
	 * @param checkbox
	 * @param radio
	 * @param list
	 * @param combo
	 * @param input
	 * @param textarea
	 * 
	 */	
	public static function setFormSkins (	button:Class,
											checkbox:Class,
											radio:Class,
											list:Class,
											combo:Class,
											input:Class,
											textarea:Class
											):void
	{
		if (!_formControlClasses)
		{
			_formControlClasses = new FormControlClasses();
		}

		_formControlClasses.button = button;
		_formControlClasses.checkbox = checkbox;
		_formControlClasses.radio = radio;
		_formControlClasses.list = list;
		_formControlClasses.combo = combo;
		_formControlClasses.input = input;
		_formControlClasses.textarea = textarea;
	}
	
	
}
}

import peace.htmllooker.Htmllooker;
import peace.htmllooker.HtmlUtils;
import peace.gui.controls.buttons.PushButton;
import peace.gui.controls.buttons.Checkbox;
import peace.gui.controls.list.ListBox;
import peace.gui.controls.list.ComboBox;
import peace.gui.controls.text.InputField;
import peace.htmllooker.NodeFactory;

internal class XmlPreprocessor
{
	/**
	 * До них нам нужно поднять все узлы модулей 
	 */	
	
	private var f:NodeFactory;
	/**
	 * 
	 * @param nodeFactory
	 * 
	 */	
	public function XmlPreprocessor (nodeFactory:NodeFactory)
	{
		f = nodeFactory;
	}
	/**
	 * Тюнинг xhtml для нормальной работы лукера
	 *  
	 * @param xml
	 * @return 
	 * 
	 */	
	public function process (xml:XML):XML
	{
		// если пришол xml с неймспейсами		
		// чистим неймспейсы
		if (xml.namespaceDeclarations().length > 0)
		{
			var s:String = xml.toXMLString();
			s = s.replace(/ xmlns.*?\".*?\"/gims, "");
			xml = XML (s);
		}
		else
		{
			xml = xml.copy();
		}
		
		toLowerCase(xml);
		
		var e:XML;
		// если мы не боди, то ищем боди. Если не нашли, оборачиваем сами
		if (xml.localName() != "body")
		{
			e = xml..body[0];
			if (!e)
			{
				e = <body/>;
				e.* = xml;
			}
			xml = e;
		}
		
		/////////////////////
		// чиним разные мелочи
		/////////////////////
		
		// на все ссылки вешаем дополнительное событие
		// которое обрабатываем в корневом Htmllooker
		var links:XMLList = xml..a;
		var target:String;
		for each (e in links)
		{
			target = (e.@target == undefined) ? "_self" : e.@target;
			e.@href = "event:" + target + Htmllooker.LINK_TARGET_DELIMITER + e.@href;
		}

		// ссылки на картинках
		var linkimages:XMLList = xml..a.img;
		for each (e in linkimages)
		{
			// переносим ссылочные аттрибуты в картинку беспесды
			e.@href = e.parent().@href;
		}
		
		// цвет текста в font
		var fontcolors:XMLList = xml..font.@color;
		for each (e in fontcolors)
		{
			e.parent().@color = HtmlUtils.parseColor (String(e));
		}
		
		///////////////////////////////////////////
		// поднимаем узлы модулей до родительских
		///////////////////////////////////////////
		var parentNodes:Array = f.parentNodeNames;
		var nodeClasses:Object = f.nodeClasses;
		var modules:XMLList = new XMLList();
		var nodeName:String;
		for (nodeName in nodeClasses)
		{
			modules += xml.descendants(nodeName);
		}
		
		for each (e in modules)
		{
			if (!e.parent()) continue;
			
			for (; parentNodes.indexOf(e.parent().localName()) == -1 ;)
			{
				e = levelUp (e);
			}
		}
		
		/////////////////////
		// чиним разные мелочи после переформатирования
		/////////////////////

		return xml;
	}
	/**
	 * <ASS> --> <ass> 
	 * @param xml
	 * 
	 */	
	private function toLowerCase (xml:XML):void
	{
		if (xml.nodeKind() != "text")
		{
			xml.setLocalName (xml.localName().toLowerCase());
		}
		
		var list:XMLList = xml..*.(nodeKind()=="element");
		var e:XML;
		for each (e in list)
		{
			e.setLocalName(e.localName().toLowerCase());
		}
	}
	/**
	 * Поднимаем узел на уровень выше (на родительский) 
	 * 
	 * @param n
	 * @return новая ссылка на переданный узел (специфика e4x)  
	 * 
	 */	
	private function levelUp (n:XML):XML
	{
		var parent:XML = n.parent();
		var newParent:XML = parent.parent();
		
		if (! newParent)
		{
			return n;
		} 
		
		var i:int = n.childIndex();
		// если до меня есть ещё дети
		if (i > 0)
		{
			var p1:XML = parent.copy();
			p1.* = p1.*.(childIndex() < i);
			newParent.insertChildBefore(parent, p1);
		}
		
		// если после меня остались дети
		if (parent.*.length() > i+1)
		{
			// удаляем предыдущих детей и целевой 
			var j:int;
			for (j = 0; j <= i; j++)
			{
				delete parent.*[0];
			}
			
			newParent.insertChildBefore(parent, n);
			
			// ссылка на новую копию узла
			n = newParent.*[parent.childIndex()-1];
		}
		// если я последний ребенок
		// то убиваем родителя
		else
		{
			newParent.insertChildBefore(parent, n);
			
			// ссылка на новую копию узла
			n = newParent.*[parent.childIndex()-1];

			i = parent.childIndex();
			delete newParent.*[i];
		}
		
		return n;
			
	}

}
/**
 * Скины для контролов формы
 * 
 */
internal class FormControlClasses
{
	public var button:Class = PushButton;
	public var checkbox:Class = Checkbox;
	public var radio:Class = Checkbox;
	public var list:Class = ListBox;
	public var combo:Class = ComboBox;
	public var input:Class = InputField;
	public var textarea:Class = InputField;
}
